/**
 *  @file Process.cpp
 */
#if defined(WIN32)
#   include <windows.h>
#elif defined(linux)
#   include <stdio.h>
#   include <stdlib.h>
#   include <unistd.h>
#   include <sys/wait.h>
#   include <errno.h>
#endif
#include "../common/CppflibMain.h"
#include "../common/CppMemPool.h"
#include "../common/UtilFuncT.h"
#include "../threading/Thread.h"
#include "Process.h"
#include "../text/CFString.h"

using namespace cppflib::threading;

namespace cppflib
{

namespace os
{

#if defined(WIN32)
PRIVATE HANDLE __GetProcess(ptr_t hProcess)
{
   return reinterpret_cast<HANDLE>(hProcess);
}
#elif defined(linux)
PRIVATE pid_t __GetProcess(ptr_t hProcess)
{
   return static_cast<pid_t>(hProcess);
}

#endif

Process::Process()
{
   this->hProcess = 0;
}

Process::Process(ptr_t hProcess)
{
   this->hProcess = hProcess;
}

Process::~Process()
{
   Close();
}

void Process::Close()
{
#if defined(WIN32)
   if (hProcess) {
      ::CloseHandle(__GetProcess(hProcess));
   }
#endif

   hProcess = 0;
}

/**
 *  Wait for child to exit indefinitely
 *
 *  @param [out] exitCode -- exit status code from child process
 */
bool Process::WaitForExit(u32_t &exitCode)
{
   return WaitForExit(0x7fffffff, exitCode);
}

/**
 *  Wait for child to exit with a timeout
 *
 *  @param [out] exitCode -- exit status code from child process if exit normally
 *
 *  @return true if exit normally
 */
bool Process::WaitForExit(int timeout, u32_t &exitCode)
{
   if (timeout < 0)
      return false;

   exitCode = 0xffffffff;

#if defined(WIN32)
   if (::WaitForSingleObject(__GetProcess(hProcess), timeout) == WAIT_OBJECT_0) {
      DWORD code = 0;
      if (::GetExitCodeProcess(__GetProcess(hProcess), &code))
         exitCode = code;

      return true;
   }

   return false;
#elif defined(linux)
   //
   // Check whether child exited by polling with 'waitpid' which is the most portable
   // and thread safe way as far as I know.
   //
   const int SLEEPINTERVAL = 1500;
   bool isOK = false;
   int status = 0;
      
   do {
      status = 0;
      // check if the PID the same as our child's PID
      pid_t pid = waitpid(__GetProcess(hProcess), &status, WNOHANG);
      if (pid == 0) {
         if (timeout <= 0)
            break;

         int waitTime = cppflib::Min<int>(timeout, SLEEPINTERVAL);
         Thread::Sleep(waitTime);
         timeout -= waitTime;
      }
      else if (pid == __GetProcess(hProcess)) {         
         if (WIFEXITED(status)) {   // exit normally ?
            exitCode = WEXITSTATUS(status);
            isOK = true;
         }
         break;
      }
      else {
         // other errors
         break;
      }
      
   } while (true);

   return isOK;
#endif
}

/**
 *  Get process ID
 */
u32_t Process::GetProcId(void)
{
#if defined(WIN32)
   return ::GetProcessId(__GetProcess(hProcess));
#elif defined(linux)
   return __GetProcess(hProcess);
#endif
}

/**
 *  Launch program specified by exePath
 *
 *  @param [in] exePath -- absolute or relative path 
 */
Process * Process::RunProc(pcwstr_t exePath)
{
   return RunProc(exePath, NULL);
}

/**
 *  Launch program specified by exePath
 *
 *  For both Windows and Linux, double quote the argument if space exists in between it
 *
 *  @param [in] exePath -- absolute or relative path 
 *  @param [in] args -- space separated list of command line arguments NOT including exe name itself 
 *                         (Pass NULL if none)
 */
Process * Process::RunProc(pcwstr_t exePath, pcwstr_t args)
{

#if defined(WIN32)
   // prepare structures
   STARTUPINFO si = {0};
   PROCESS_INFORMATION pi = {0};

   si.cb = sizeof(si);

   // prepare command line
   CFString cmdLine(exePath);
   if (args)
      cmdLine.AppendFormat(_S(" %s"), args);

   wc_t * pCmdLine = new (cppmempool::NEWPTR) wc_t[cmdLine.GetLength() + 1];
   CFString::Memcpy(pCmdLine, cmdLine.operator pcwstr_t(), (cmdLine.GetLength() + 1) * sizeof(wc_t));
   
   bool isOK = ::CreateProcess(
                        NULL, 
                        pCmdLine,              // must pass a modifiable string
                        NULL, 
                        NULL, 
                        FALSE,                 // inherit handles or not
                        NORMAL_PRIORITY_CLASS, // priority
                        NULL,                  // environment variables
                        NULL,                  // current directory
                        &si, 
                        &pi) ? true : false;

   Process * pProc = NULL;
   if (isOK) {
      if (pi.hProcess)
         pProc = new Process(reinterpret_cast<ptr_t>(pi.hProcess));
      if (pi.hThread) ::CloseHandle(pi.hThread);   // thread handle not required
   }

   cppmempool::Release(pCmdLine);

   return pProc;

#elif defined(linux)

   collections::ListT<pcwstr_t> argList;
   
   //
   // chop the space separated arguments in 'args' and pack them into 'argList'.
   // Also, arguments quoted inside double quote (") are supported
   //
   if (args) {
      int lenArgs = static_cast<int>(CFString::WStrlen(args));
      int index = 0;
      
      while (index < lenArgs) {
         wc_t c; 

         // skip all white spaces
         while (index < lenArgs) {
            c = args[index];
            if (c == _S(' ') || c == _S('\t'))
               ++index;
            else
               break;
         }

         if (index >= lenArgs)
            break;

         // get start and end of this argument
         int start = 0;
         bool isQuoted = false;

         if (c == _S('"')) {
            // deal with argument wrapped inside double quotes

            isQuoted = true;
            start = ++index;
            while (index < lenArgs) {
               c = args[index];
               if (c != _S('"'))
                  ++index;
               else
                  break;
            }

         }
         else {
            start = index;
            while (index < lenArgs) {
               c = args[index];
               if (c != _S(' ') && c != _S('\t'))
                  ++index;
               else
                  break;
            }

         }

         // copy it to the list
         if (start < index - 1) {
            wc_t * pStr = new (cppmempool::NEWPTR) wc_t[index - start + 1];
            CFString::Memcpy(pStr, args + start, (index - start) * sizeof(wc_t));
            argList.AddTail(pStr);
         }

         if (isQuoted)
            ++index;
      } // end of while
   }

   Process * pProc = RunProc(exePath, argList);

   // clear list
   while (argList.GetCount() > 0) {
      pwstr_t pStr = const_cast<pwstr_t>(argList.RemoveAt(0));
      cppmempool::Release(pStr);
   }

   return pProc;

#endif
}

/**
 *  Launch program specified by exePath
 *
 *  For Windows, it's best to double quote the argument if space exists in between it
 *
 *  @param [in] exePath -- absolute or relative path 
 *  @param [in] argList -- A list of arguments 
 */
Process * Process::RunProc(pcwstr_t exePath, collections::ListT<pcwstr_t> &argList)
{
#if defined(WIN32)
   CFString cmdArgs;
   
   for (int i = 0; i < static_cast<int>(argList.GetCount()); ++i) {
      CFString arg(argList.Get(i));

      if (!arg.StartsWith(_S('"')) && arg.Find(_S(' ')) >= 0)  // add double quotes if space is found 
         cmdArgs.AppendFormat(_S(" \"%s\""), arg.operator pcwstr_t());
      else
         cmdArgs.AppendFormat(_S(" %s"), arg.operator pcwstr_t());
   }

   return RunProc(exePath, (cmdArgs.GetLength() > 0) ? 
                                    cmdArgs.operator pcwstr_t() : static_cast<pcwstr_t>(NULL));

#elif defined(linux)

   pid_t pid = fork();
   if (pid == 0) {
      // child process
      
      // prepare command line arguments
      char ** argv = new (cppmempool::NEWPTR) char* [argList.GetCount() + 2];

      // argv[0] points to name of exe
      CFString cmdExe(exePath);
      int pos = cmdExe.ReverseFind(_S('/'));
      if (pos >= 0) {
         CFString t = cmdExe.Mid(pos + 1, -1);
         argv[0] = t.ToUtf8String();
      }
      else {
         argv[0] = cmdExe.ToUtf8String();
      }

      // rest of cmd arguments
      for (int i = 0; i < argList.GetCount(); ++i) {
         CFString t(argList.Get(i));
         argv[i + 1] = t.ToUtf8String();
      }

      // mark NULL at the end
      argv[argList.GetCount() + 1] = NULL;

      // hopefully, 'execv' won't return 
      if (execv(cmdExe.ToUtf8String(), argv) < 0)
         exit(1); // if return, exit process immediately
   }
   

   // original process goes here
   return (pid > 0) ? new Process(static_cast<ptr_t>(pid)) : NULL;

#endif
}



}  // end of namespace os


}  // end of namespace cppflib

